using System;
using System.Collections.Generic;
using System.Linq;
using Dimebrain.TweetSharp.Core.Extensions;

namespace twimillar2
{
	public static class Twimilar2
	{
		private static Dictionary<string, Dictionary<string, double>> _prefs;

		public static IUserConfig Go()
		{
			return new UserConfig(InitializePrefs, GetTopWords);
		}

		private static IEnumerable<KeyValuePair<double, string>> GetAroundFor(string userName)
		{
			return TopMathces(_prefs, userName, 10, SimDistance);
		}

		private static IEnumerable<KeyValuePair<double, string>> GetTopWords(string userName)
		{
			var dictionary = new Dictionary<string, double>();
			foreach (string person in _prefs.Keys)
			{
				foreach (string word in _prefs[person].Keys)
				{
					if (!dictionary.Keys.Contains(word))
						dictionary[word] = 0;
					dictionary[word] += _prefs[person][word];
				}
			}
			return dictionary.OrderByDescending(x => x.Key).Take(10).ToList()
				.Map(x => new KeyValuePair<double, string>(x.Value, x.Key));
		}

		private static void InitializePrefs(string userName)
		{
			_prefs = new Dictionary<string, Dictionary<string, double>>();
			_prefs.LoadForUser(userName);

			userName.GetFriends().Map(x => _prefs.LoadForUser(x));
			_prefs[userName].Keys.ToList().ForEach(word => word.GetUsersWhoTwitThis().Map(x => _prefs.LoadForUser(x)));
		}

		private static double SimDistance(IDictionary<string, Dictionary<string, double>> prefs, string person1, string person2)
		{
			var si = new Dictionary<string, double>();
			var prefs1 = prefs[person1].Keys;
			var prefs2 = prefs[person2].Keys;

			prefs1.Filter(x => prefs2.Contains(x)).Map(x => si[x] = 1);

			if (si.Count == 0) return 0;

			var sumOfSquares = prefs1.Filter(x => prefs2.Contains(x))
				.Map(x => Math.Pow(prefs[person1][x] - prefs[person2][x], 2))
				.Reduce<double, double>(0, (x, y) => x + y);

			return 1 / (1 + sumOfSquares);
		}

		private static double SimPearson(IDictionary<string, Dictionary<string, double>> prefs, string person1, string person2)
		{
			var si = new Dictionary<string, double>();
			var prefs1 = prefs[person1].Keys;
			var prefs2 = prefs[person2].Keys;

			prefs1.Filter(x => prefs2.Contains(x)).Map(x => si[x] = 1);

			if (si.Count == 0) return 0;

			var n = si.Count;

			var sum1 = si.Keys.Map(x => prefs[person1][x])
				.Reduce<double, double>(0, (x, y) => x + y);
			var sum2 = si.Keys.Map(x => prefs[person2][x])
				.Reduce<double, double>(0, (x, y) => x + y);

			var sum1Sq = si.Keys.Map(x => prefs[person1][x])
				.Reduce<double, double>(0, (x, y) => Math.Pow(x, 2) + y);

			var sum2Sq = si.Keys.Map(x => prefs[person2][x])
				.Reduce<double, double>(0, (x, y) => Math.Pow(x, 2) + y);

			var pSum = si.Keys
				.Reduce<string, double>(0, (x, y) => prefs[person1][x] * prefs[person2][x] + y);

			var num = pSum - (sum1 * sum2 / n);
			var den = Math.Sqrt((sum1Sq - Math.Pow(sum1, 2) / n) * (sum2Sq - Math.Pow(sum2, 2) / n));

			if (den == 0)
				return 0;

			return num / den;
		}

		private static IEnumerable<KeyValuePair<double, string>> TopMathces(IDictionary<string, Dictionary<string, double>> prefs, string person, int count, Func<IDictionary<string, Dictionary<string, double>>, string, string, double> similarity)
		{
			return prefs.Filter(x => x.Key != person)
				.Map(x => new KeyValuePair<double, string>(similarity(prefs, person, x.Key), x.Key))
				.OrderByDescending(x => x.Key).Take(count).ToList();
		}

		private static IEnumerable<KeyValuePair<double, string>> GetRecommendations(IDictionary<string, Dictionary<string, double>> prefs, string person, Func<IDictionary<string, Dictionary<string, double>>, string, string, double> similarity)
		{
			var totals = new Dictionary<string, double>();
			var simSums = new Dictionary<string, double>();

			foreach (string other in prefs.Keys)
			{
				if (person == other) continue;

				var sim = similarity(prefs, person, other);

				if (sim <= 0) continue;

				foreach (string item in prefs[other].Keys)
				{
					if (!prefs[person].Keys.Contains(item) || prefs[person][item] == 0)
					{
						totals[item] = 0;
						totals[item] += prefs[other][item] * sim;
						simSums[item] = 0;
						simSums[item] += sim;
					}
				}
			}

			return totals
				.Map(item =>
					new KeyValuePair<double, string>(
						item.Value / simSums[item.Key], item.Key
						)).OrderByDescending(x => x.Key).ToList();
		}
	}
}